"""
网络编程部分
https://blog.csdn.net/qq_36553572/article/details/103899384
"""
import logging
import re
import socket
import threading
import traceback

from config.conf import BlackList, Config, WhiteList
from utils.constants import Http, BlockList

logger = logging.getLogger()


def tcp_server():
    """
    开启一个tcp服务端, 获取本机的网络请求
    :return:
    """

    proxy_server = Config().local_proxy_server
    host = proxy_server.split(':')[0]
    port = int(proxy_server.split(":")[1])
    addr = (host, port)
    max_listen = 5

    try:
        proxy_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        proxy_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        proxy_server.bind(addr)
        proxy_server.listen(max_listen)
        logger.info(f"Proxy server is listening on {host}:{port}")
        while True:
            client_socket, _ = proxy_server.accept()
            threading.Thread(target=handle_client, args=(client_socket,)).start()
    except Exception as e:
        logger.error(traceback.format_exc())
        raise ValueError(f"服务启动失败，请联系管理员\n{e}")


def handle_client(client_socket):
    """
    获取浏览器发出的请求，并进行解析
    """
    try:
        request_packet = client_socket.recv(1024).decode()
        if len(request_packet) == 0:
            return

        method = request_packet.split()[0]
        domain = re.findall("Host: (.*)\r\n", request_packet)[0]

        target_host = domain.split(":")[0] if ":" in domain else domain
        target_port = int(domain.split(":")[-1]) if ":" in domain else 80
        proxy_host = Config().target_proxy_server.split(":")[0]
        proxy_port = int(Config().target_proxy_server.split(":")[1])
        target_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

        block_lst = BlockList
        white_lst = WhiteList().value
        black_lst = BlackList().value
        if filter_domain(domain, block_lst):
            # 如果在拦截名单中，则直接返回
            logger.info(f"拦截：{domain}")
            return

        if Config().proxy_pattern == "global":
            # 尊贵的vip用户,所有请求不进行判断
            logger.info(f"vip: {domain} 连接到 {proxy_host}:{proxy_port}")
            target_socket.connect((proxy_host, proxy_port))
            target_socket.sendall(request_packet.encode())
        elif filter_domain(domain, white_lst) and not filter_domain(domain, black_lst):
            # 如果在白名单中，进行代理，为了安全再检查一遍是否在黑名单中(感觉必要性好像不大，lol)
            logger.info(f"代理：{domain} 连接到 {proxy_host}:{proxy_port}")
            target_socket.connect((proxy_host, proxy_port))
            target_socket.sendall(request_packet.encode())
        elif method == Http.MethodConnect.value:
            # 如果是CONNECT请求头，告诉客户端连接已建立
            logger.info(f"本地connect请求：{domain} 连接到 {target_host}:{target_port}")
            target_socket.connect((target_host, target_port))
            client_socket.send(b'HTTP/1.1 200 Connection Established\r\n\r\n')
        else:
            # 如果不用进行代理，也不是connect请求头的情况下
            logger.info(f"本地：{domain} 连接到 {target_host}:{target_port}")
            target_socket.connect((target_host, target_port))
            target_socket.sendall(request_packet.encode())

        # 目标服务器与本地进行互传数据
        client_to_target = threading.Thread(target=transfer_data, args=(client_socket, target_socket))
        client_to_target.start()
        transfer_data(target_socket, client_socket)
        client_to_target.join()

        client_socket.close()
        target_socket.close()
    except Exception as e:
        # traceback.print_exc()
        logger.error(f"{threading.current_thread().name} {e}失败{domain})")


def transfer_data(source, destination):
    """
    进行传输数据
    """
    while True:
        data = source.recv(4096)
        if len(data) == 0:
            break
        destination.send(data)


def filter_domain(domain, lst_domain) -> bool:
    """判断当前请求的域名是否在黑名单/白名单/拦截名单中"""
    is_ = False

    for item in lst_domain:
        result = re.search(item, domain)
        if result is not None:
            is_ = True
            break

    return is_


def run():
    """入口，开始运行"""
    tcp_server()

